/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.core.ui.dialogs;

import java.util.HashSet;
import java.util.Iterator;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.uml2.uml.Classifier;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Namespace;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.TemplateableElement;
import org.eclipse.uml2.uml.Type;

import com.cea.papyrus.core.PapyrusPlugin;
import com.cea.papyrus.core.utils.LookForElement;

/**
 * Dialog that allows the user to select a type from the model and its imports
 */
public class TypedElementTreeSelectionDialog extends Dialog{

	/** Associated Type */
	protected Type type;

	/** The selection tree */
	protected Tree tree;

	/** the top package, ie profile/package/model root of the model */
	protected Package model;

	/** 
	 * 
	 */
	protected TypeFilter filter ;
	
	/**
	 * Creates a new TypedElementTreeSelectionDialog.
	 * @param parent the parent shell of the dialog
	 * @param model the top package of the model
	 */
	public TypedElementTreeSelectionDialog(Shell parent, Package model){
		super(parent);
		this.model=model;
		filter = new TypeFilter(null, null) ;
	}
	
	/**
	 * Creates a new TypedElementTreeSelectionDialog.
	 * @param parent the parent shell of the dialog
	 * @param model the top package of the model
	 */
	public TypedElementTreeSelectionDialog(Shell parent, Package model, EClass filterMetaclass){
		super(parent);
		this.model=model;
		filter = new TypeFilter(filterMetaclass, null) ;
	}
	
	/**
	 * Creates a new TypedElementTreeSelectionDialog.
	 * @param parent the parent shell of the dialog
	 * @param model the top package of the model
	 */
	public TypedElementTreeSelectionDialog(Shell parent, Package model, EClass filterMetaclass, Type constrainingClassifier){
		super(parent);
		this.model=model;
		filter = new TypeFilter(filterMetaclass, constrainingClassifier) ;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell)
	 */
	@Override
	protected void configureShell(Shell shell) {
		super.configureShell(shell);
		shell.setText("Type Selection");
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
	 */
	protected Control createDialogArea(Composite parent){
		Composite composite = (Composite)super.createDialogArea(parent);
		tree = new Tree(composite, SWT.SINGLE | SWT.BORDER | SWT.V_SCROLL);

		TreeItem item = new TreeItem(tree, SWT.NONE);
		item.setText(model.getName());
		item.setData(model);
		item.setImage(PapyrusPlugin.UML_LABEL_PROVIDER.getImage(model));
		buildImportTreeList(item, model);

		// build basic imported packages list
		HashSet<Package> importedPackages = LookForElement.buildImportedTopPackagesList(model, new HashSet<Package>());
		
		// extends this list to all imported packages  (import from import, etc...)
		Iterator<Package> packages = LookForElement.buildExtendedImportedPackagesList(importedPackages, model).iterator();

		while (packages.hasNext()) {
			Package currentPackage = (Package) packages.next();
			item = new TreeItem(tree, SWT.NONE);
			item.setText(PapyrusPlugin.UML_LABEL_PROVIDER.getText(currentPackage, true));
			item.setData(currentPackage);
			item.setImage(PapyrusPlugin.UML_LABEL_PROVIDER.getImage(currentPackage));
			buildImportTreeList(item, currentPackage);
		}
		tree.setLayoutData(new GridData(300, 300));

		tree.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e){
				setSelection(e);
			}

			public void widgetDefaultSelected(SelectionEvent e) {
			}
		});

		return composite;
	}
	
	/**
	 * Sets the selection, i.e. the element that will be returned by the dialog.
	 * @param e the selection event
	 */
	protected void setSelection(SelectionEvent e) {
		Widget selectedWidget = e.item;
		if(selectedWidget.getData() instanceof Type) {
			type = (Type)selectedWidget.getData();
			getButton(IDialogConstants.OK_ID).setEnabled(true) ;
		} else {
			// sets association to null, if selected element is not an type.
			// erase a previous selected type
			// example: select type T1, then a package
			// if association is not set to null, it keeps the type T1 as result
			// whereas the package is selected
			// TODO should improve the dialog: Ok should not be enabled when type is null
			// TODO should improve the dialog: selection should be initialized depending on 
			// the original type( ex open  a window on an already typed element)
			type = null;
			getButton(IDialogConstants.OK_ID).setEnabled(false) ;
		}
	}

	/**
	 * This method recursively searches the given Namespace for Type elements.
	 * As new Type elements are discovered, they are added to the element tree
	 * rooted by <code>elemTree</code>. 
	 *
	 * @param elemTree nodes representing the discovered types are added below
	 *     this parent treeItem.
	 * @param namespace the Namespace to explore
	 */
	protected boolean buildImportTreeList(TreeItem elemTree, Namespace namespace) {
		// Whether the search has found any types. if a subtree of the model
		// does not contain any Types, then that branch of the tree is discarded
		// from the widget tree.
		boolean typeFound = false;

		for (Element elem : namespace.getOwnedElements()) {
			if (elem instanceof TemplateableElement) {
				TemplateableElement templateableElement = (TemplateableElement) elem ;
				if (templateableElement.getOwnedTemplateSignature()!=null) {
					buildTemplateTreeList(elemTree, templateableElement);
				}
			}
			
			if (elem instanceof Type && filter.isValidType((Type) elem)) {
				typeFound = true;
				TreeItem item = new TreeItem(elemTree, SWT.NONE);
				item.setText(PapyrusPlugin.UML_LABEL_PROVIDER.getText(elem, true));
				item.setData(elem);
				item.setImage(PapyrusPlugin.UML_LABEL_PROVIDER.getImage(elem));
				
				if (elem instanceof Namespace)
					buildImportTreeList(item, (Namespace)elem);
			}
			else if (elem instanceof Namespace) {
				TreeItem item = new TreeItem(elemTree, SWT.NONE);
				item.setText(PapyrusPlugin.UML_LABEL_PROVIDER.getText(elem, true));
				item.setData(elem);
				item.setImage(PapyrusPlugin.UML_LABEL_PROVIDER.getImage(elem));
				boolean childTypesFound = buildImportTreeList(item, (Namespace)elem);
				if (!childTypesFound)
					item.dispose();
				typeFound= true;
			}
		}
		return typeFound;
	}

	/**
	 * Builds the list of types provided by the templateable element
	 * @param elemTree the current Tree element
	 * @param templateableElement the templatable element
	 */
	protected void buildTemplateTreeList(TreeItem elemTree, TemplateableElement templateableElement){
		com.cea.papyrus.umlutils.TemplateSignature templateSignatureUtil = new com.cea.papyrus.umlutils.TemplateSignature(templateableElement.getOwnedTemplateSignature()) ;
		Iterator<com.cea.papyrus.umlutils.Type> ite= templateSignatureUtil.getAccessibleTypes().iterator();
		while(ite.hasNext()){
			Element element=((com.cea.papyrus.umlutils.Type)ite.next()).getUml2Element();
			if (filter.isValidType((Type)element)) {
				TreeItem item = new TreeItem(elemTree, SWT.NONE);
				item.setText(PapyrusPlugin.UML_LABEL_PROVIDER.getText(element, true));
				item.setData(element);
				item.setImage(PapyrusPlugin.UML_LABEL_PROVIDER.getImage(element));
			}
		}
	}
	
	/**
	 * Returns the selected Type.
	 * @return the selected type 
	 */
	public Type getResult(){
		return type;
	}
	
	
	
	@Override
	protected void cancelPressed() {
		// TODO Auto-generated method stub
		super.cancelPressed();
		type = null ;
	}



	private class TypeFilter {
		
		private EClass metaclassFilter ;
		private org.eclipse.uml2.uml.Type constrainingClassifier ;
		
		public TypeFilter(EClass metaclassFilter, Type constrainingClassifier) {
			this.metaclassFilter = metaclassFilter ;
			this.constrainingClassifier = constrainingClassifier ;
		}
		
		public boolean isValidType(Type candidate) {
			
			if ((candidate.getName() == null) || candidate.getName().equals("")) {
				return false ;
			}
			
			if (metaclassFilter == null)
				return true ;
			
			if ((candidate.eClass().getEAllSuperTypes().contains(metaclassFilter) 
					|| metaclassFilter == candidate.eClass()) 
					&& (candidate instanceof Classifier && !((Classifier)candidate).isTemplate()))
				if (constrainingClassifier == null)
					return true ;
				else
					return candidate == constrainingClassifier || ((Classifier)candidate).allParents().contains(constrainingClassifier) ;
//			if (candidat instanceof Class.forName(metaclassFilter.getName()))
			return false ;
		}
	}
}
